//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using LargoCommon.Abstract;
using LargoCommon.Interfaces;
using LargoCommon.Midi;
namespace LargoCommon.Music
{
///
/// Melodic Tone Collection.
///
[Serializable]
[XmlRoot]
public sealed class MusicalToneCollection : Collection {
#region Constructors
///
/// Initializes a new instance of the MusicalToneCollection class.
///
public MusicalToneCollection() {
}
///
/// Initializes a new instance of the MusicalToneCollection class.
///
/// Given list.
/// Set Ordinal Index.
public MusicalToneCollection(IEnumerable givenList, bool setOrdinalIndex) {
Contract.Requires(givenList != null);
if (givenList == null) {
return;
}
foreach (var tone in givenList) {
this.AddTone(tone, setOrdinalIndex);
}
}
///
/// Initializes a new instance of the MusicalToneCollection class.
///
/// Given list.
public MusicalToneCollection(IList givenList)
: base(givenList) {
}
///
/// Initializes a new instance of the MusicalToneCollection class.
///
/// Given tones.
/// Rhythmical bit-range of the tones.
/// Set Ordinal Index.
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1126:PrefixCallsCorrectly", Justification = "Reviewed.")]
public MusicalToneCollection(IEnumerable givenTones, BitRange range, bool setOrdinalIndex) {
if (givenTones == null) {
return;
}
// ReSharper disable once LoopCanBePartlyConvertedToQuery
foreach (var mtone in givenTones) {
var toneRange = mtone.BitRange; //// (barNumber);
if (toneRange == null) {
continue;
}
if (range != null) { //// 2016/07
var interRange = toneRange.IntersectionWith(range);
if (interRange == null || interRange.IsEmpty) {
continue;
}
}
if (mtone is MusicalTone tone)
{
this.AddTone(tone, setOrdinalIndex);
}
}
}
#endregion
#region Properties
///
/// Gets Tone Key.
///
/// General property.
public string ToneKey {
get {
var sb = new StringBuilder();
foreach (var s in this.Select(mt => MusicalProperties.GetNoteName(mt.Pitch.Element) + mt.Pitch.Octave)) {
sb.Append(s);
}
return sb.ToString();
}
}
/// Gets list of all already defined tones.
/// Property description.
public MusicalToneCollection FormalTones {
get {
var elements = (from mt in this select mt.Pitch.Element).Distinct();
var firstTone = this.FirstOrDefault();
if (firstTone?.Pitch == null) {
return null;
}
var harSystem = firstTone.Pitch.HarmonicSystem;
var ftc = new MusicalToneCollection();
foreach (var p in elements.Select(element => new MusicalPitch(harSystem, 0, element))) {
ftc.AddTone(new MusicalTone(p, firstTone.BitRange, firstTone.Loudness, firstTone.BarNumber), true); //// (firstTone.BarNumberFrom)
}
return ftc;
}
}
/// Gets list of all already defined tones.
/// Property description.
public MusicalToneCollection ValidToneList { //// MusicalToneCollection
get {
//// 2011/11 added && mt.IsTrueTone
var tones = from mt in this
where !mt.IsEmpty && mt.IsTrueTone
orderby mt.Pitch.SystemAltitude
select mt;
var collection = new MusicalToneCollection(tones, false);
return collection; //// new MusicalToneCollection(list.ToList());
}
}
/// Gets String representation of the collection.
/// Property description.
[XmlAttribute]
public string ToneSchema {
get {
var str = new StringBuilder();
byte i = 0;
foreach (var s in from mt in this where mt != null && mt.IsTrueTone select mt.Pitch.ToString()) {
str.Append(s);
i++;
if (i != this.Count) {
str.Append(",");
}
}
return str.ToString();
}
}
/// Gets For melodical tracks only.
/// Property description.
public MusicalOctave MeanOctave {
get {
const int limit = 100;
var sumNotes = 0;
var numNotes = 0;
foreach (var mt in this.Where(mt => mt?.Pitch != null)) {
sumNotes += mt.Pitch.MidiKeyNumber;
numNotes++;
if (numNotes >= limit) {
break;
}
}
var meanNote = numNotes != 0 ? (byte)((float)sumNotes / numNotes) : (byte)0;
var firstTone = this.FirstOrDefault();
MusicalPitch mp;
if (firstTone?.Pitch != null) {
var harSystem = firstTone.Pitch.HarmonicSystem;
mp = harSystem.GetPitch(meanNote);
}
else {
mp = new MusicalPitch(meanNote);
}
return (mp != null) ? (MusicalOctave)mp.Octave : MusicalOctave.None;
}
}
/// Gets BandType.
/// Property description.
public MusicalBand MeanBandType {
get {
const int limit = 100;
var sumNotes = 0;
var numNotes = 0;
foreach (var mt in this.Where(mt => mt?.Pitch != null)) {
sumNotes += mt.Pitch.MidiKeyNumber;
numNotes++;
if (numNotes >= limit) {
break;
}
}
var meanNote = numNotes != 0 ? (byte)((float)sumNotes / numNotes) : (byte)0;
return MusicalProperties.BandOfPitch(meanNote);
}
}
/// Gets Mean Duration.
/// Property description.
public float MeanDuration {
get {
const int limit = 100;
float sumDur = 0;
var numNotes = 0;
foreach (var mt in
this.Where(mt => mt != null && !mt.IsEmpty && mt.RhythmicOrder != 0)) {
sumDur += (float)mt.Duration / mt.RhythmicOrder;
numNotes++;
if (numNotes >= limit) {
break;
}
}
var meanDur = numNotes != 0 ? sumDur / numNotes : 0;
return meanDur;
}
}
/// Gets Mean Loudness.
/// Property description.
public MusicalLoudness MeanLoudness {
get {
const int limit = 100;
float sumLoudness = 0;
var numNotes = 0;
foreach (var mt in this.Where(mt => mt != null && mt.Loudness > 0)) {
sumLoudness += (short)mt.Loudness;
numNotes++;
if (numNotes >= limit) {
break;
}
}
var meanLoudness = numNotes != 0 ? sumLoudness / numNotes : 0;
return (MusicalLoudness)(byte)Math.Round(meanLoudness);
}
}
///
/// Gets a value indicating whether Contains Halftone.
///
/// Property description.
public bool ContainsHalftone {
get {
for (var i = 1; i < this.Count; i++) {
var t0 = this.ElementAt(i - 1);
var t1 = this.ElementAt(i);
//// Sequences containing halftones are considered to be melodic
if (t0 == null || t1 == null || t0.Pitch == null || t1.Pitch == null) {
continue;
}
if (Math.Abs(t0.Pitch.SystemAltitude - t1.Pitch.SystemAltitude) == 1) {
return true; //// Avoid multiple or conditional return statements.
}
}
return false;
}
}
///
/// Gets a value indicating whether is is a simple stagnation.
///
/// Property description.
public bool IsSimpleStagnation {
get {
if (this.Count != 2) {
return false;
}
var t0 = this.ElementAt(0);
var t1 = this.ElementAt(1);
if (t0 == null || t1 == null || t0.Pitch == null || t1.Pitch == null) {
return false;
}
return t0.Pitch.SystemAltitude == t1.Pitch.SystemAltitude;
}
}
#endregion
#region Static support
///
/// Guess Mel Part Type.
///
/// Band type.
/// Melodic Motion Allowed.
/// Returns value.
[JetBrains.Annotations.PureAttribute]
public static MelodicFunction GuessMelodicType(MusicalBand bandType, bool melMotionAllowed) {
MelodicFunction mpt;
switch (bandType) {
case MusicalBand.HighTones: {
mpt = melMotionAllowed ? MelodicFunction.MelodicMotion : MelodicFunction.HarmonicMotion;
break;
}
case MusicalBand.MiddleTones: {
mpt = MelodicFunction.HarmonicMotion; //// HarmonicFilling;
break;
}
case MusicalBand.BassTones: {
mpt = MelodicFunction.HarmonicBass;
break;
}
default: {
mpt = MelodicFunction.HarmonicFilling;
break;
}
}
return mpt;
}
///
/// Harmonic Mel Part Type.
///
/// Band type.
/// Returns value.
[JetBrains.Annotations.PureAttribute]
public static MelodicFunction HarmonicMelodicType(MusicalBand bandType) {
var mpt = MelodicFunction.None;
switch (bandType) {
case MusicalBand.HighTones: {
mpt = MelodicFunction.HarmonicMotion;
break;
}
case MusicalBand.MiddleTones: {
mpt = MelodicFunction.HarmonicFilling;
break;
}
case MusicalBand.BassTones: {
mpt = MelodicFunction.HarmonicBass;
break;
}
case MusicalBand.Any:
break;
case MusicalBand.BassBeat:
break;
case MusicalBand.MiddleBeat:
break;
case MusicalBand.HighBeat:
break;
//// resharper default: break;
}
return mpt;
}
#endregion
#region Public methods
/// Add on musical tone to the end of part.
/// Musical tone.
/// Set Ordinal Index.
public void AddTone(MusicalTone givenTone, bool setOrdinalIndex) {
Contract.Requires(givenTone != null);
//// if (givenTone == null) { return false; }
if (setOrdinalIndex) {
givenTone.OrdinalIndex = this.Count;
}
this.Add(givenTone);
}
///
/// Add Collection.
///
/// Given tones.
/// Set Ordinal Index.
public void AddCollection(MusicalToneCollection givenTones, bool setOrdinalIndex) {
Contract.Requires(givenTones != null);
//// if (givenTone == null) { return false; }
givenTones.ForAll(musicalTone => this.AddTone(musicalTone, setOrdinalIndex));
}
///
/// Has Any Inconsistency With Harmony.
///
/// Harmonic Structure.
/// Harmonic Range.
/// Returns value.
public bool HasAnyInconsistencyWithHarmony(BinaryStructure harmonicStructure, BitRange harmonicRange) {
if (harmonicStructure == null || harmonicRange == null) {
return false;
}
return this.Where(mt => mt != null && harmonicRange.CoverRange(mt.BitRange)).Any(mt => (mt.Pitch != null) //// (mt.BarNumberFrom)
&& !harmonicStructure.IsOn(mt.Pitch.Element));
}
///
/// Rhythmic Pattern.
///
/// Returns value.
public string RhythmicPattern() {
var sb = new StringBuilder();
foreach (var mt in this.Where(mt => mt != null)) {
sb.Append(mt.Duration.ToString(CultureInfo.CurrentCulture.NumberFormat));
sb.Append(" ");
}
return sb.ToString();
}
///
/// Determine Harmonic Struct.
///
/// Harmonic modality.
/// Returns value.
[JetBrains.Annotations.PureAttribute]
public HarmonicStructure DetermineHarmonicStruct(BinarySchema harmonicModality) {
Contract.Requires(harmonicModality != null);
//// if (harmonicModality == null) { return null; }
if (this.Count == 0) {
return null;
}
var harSystem = HarmonicSystem.GetHarmonicSystem(DefaultValue.HarmonicOrder);
var hstruct = new HarmonicStructure(harSystem, (string)null) { ToneSchema = null };
var toneValue = this.DetermineToneValues(harSystem, harmonicModality);
SortToneValues(hstruct, toneValue);
hstruct.DetermineLevel();
hstruct.DetermineBehavior();
//// string s = hstruct.ToneSchema;
return hstruct;
}
///
/// Export To Midi Events.
///
/// Event Collection.
/// Midi Instrument.
/// Bar division.
public void ExportToMidiEvents(MidiEventCollection events, byte instrument, int barDivision) {
Contract.Requires(events != null);
if (events == null) {
return;
}
//// if (channel != MidiChannel.DrumChannel) {
events.PutInstrument(0, instrument);
//// }
foreach (var mt in this.Where(mt => mt != null && mt.RhythmicOrder > 0))
{
//// RealTone rt = new RealTone(mt, instrument, channel);
mt.InstrumentNumber = instrument;
//// mt.Channel = channel;
mt.WriteTo(events, barDivision);
}
}
#endregion
#region String representation
/// String representation of the object.
/// Returns value.
public override string ToString() {
var s = new StringBuilder();
this.ForAll(musicalTone => s.Append(musicalTone));
return s.ToString();
}
#endregion
#region Private static methods
///
/// Sorts the tone values.
///
/// The harmonic structure.
/// The tone value.
private static void SortToneValues(HarmonicStructure hstruct, Dictionary toneValue) {
Contract.Requires(toneValue != null);
if (hstruct == null) {
return;
}
//// Sorts toneValues by weight
var values = toneValue.Values;
var top = (from v in values select v).OrderByDescending(v => v).Take(3).ToList();
var limit = 3; //// max number of tones in the structure
foreach (var kvp in toneValue) {
if (top.Contains(kvp.Value)) {
var elem = (byte)kvp.Key; // % harSystem.Order
hstruct.On(elem);
limit--;
}
if (limit == 0) {
break;
}
}
}
#endregion
#region Private methods
///
/// Determines the tone values.
///
/// The harmonic system.
/// The harmonic modality.
/// Returns value.
private Dictionary DetermineToneValues(HarmonicSystem harSystem, BinarySchema harmonicModality) {
Contract.Requires(harmonicModality != null);
if (harSystem == null) {
return null;
}
const float modalToneIncrease = 2.0f;
const float startingToneIncrease = 0.1f;
var toneValue = new Dictionary();
//// Assign weight value to any modalAltitude
for (byte level = 0; level < harmonicModality.Level; level++) {
int modalAltitude = harmonicModality.PlaceAtLevel(level);
float value = 0;
foreach (var mt in this) {
if (mt != null && mt.IsTrueTone && harSystem.Order != 0) {
var formalAltitude = mt.Pitch.SystemAltitude % harSystem.Order;
if (formalAltitude == modalAltitude) {
value += modalToneIncrease; //// 0.5
//// Long tones have higher values
//// value += mt.Duration - (mt.Pitch.Octave * 0.1f);
//// Starting tones have higher values
if (mt.BitFrom == 0) {
value += startingToneIncrease;
}
}
//// The tones consonant with other tones have higher values
Contract.Assume(formalAltitude - modalAltitude > short.MinValue);
var formalDistance = formalAltitude - modalAltitude;
var v = MusicalInterval.GuessSonanceValue(formalDistance);
value += v;
}
if (toneValue.ContainsKey(modalAltitude)) {
toneValue[modalAltitude] += value;
}
else {
toneValue.Add(modalAltitude, value);
}
}
}
return toneValue;
}
#endregion
}
}